package com.boge.parse.web;

import com.boge.parse.Amf3Stream;
import com.boge.parse.GameEnum;
import com.boge.socket.*;
import com.boge.util.ZlibUtil;
import flex.messaging.io.amf.Amf3Input;
import org.apache.commons.lang3.StringUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author boge
 */
public class WebSocketDecoder implements ProtocolDecoder {

    private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");
    public Charset charset;
    /**
     * 接收最大字节 2M
     */
    private long receiveMax = 1024 * 1024 * 2;
    private int maxPackLength = 100;
    /**
     * 是否是服务器
     */
    private boolean isServer;

    public WebSocketDecoder() {
        this(Charset.defaultCharset());
    }

    /**
     * @param charset 是否是服务器
     */
    public WebSocketDecoder(Charset charset) {
        this(Charset.defaultCharset(), true);
    }

    /**
     * @param charset
     * @param isServer 是否是服务器
     */
    public WebSocketDecoder(Charset charset, boolean isServer) {
        this.charset = charset;
        this.isServer = isServer;
    }

    public int getMaxLineLength() {
        return maxPackLength;
    }

    public void setMaxLineLength(int maxLineLength) {
        if (maxLineLength <= 0) {
            throw new IllegalArgumentException("maxLineLength: " + maxLineLength);
        }
        this.maxPackLength = maxLineLength;
    }

    private Context getContext(IoSession session) {
        Context ctx;
        ctx = (Context) session.getAttribute(CONTEXT);
        if (ctx == null) {
            ctx = new Context(charset);
            session.setAttribute(CONTEXT, ctx);
        }
        return ctx;
    }

    @Override
    public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
        //先获取上次的处理上下文，其中可能有未处理完的数据
        Context ctx = getContext(session);
        // 先把当前buffer中的数据追加到Context的buffer当中   
        ctx.append(in);
        dealDecode(session, ctx, out);
    }

    /**
     * 处理数据
     *
     * @param session
     * @param ctx
     * @param out
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void dealDecode(IoSession session, Context ctx, ProtocolDecoderOutput out) throws Exception, ClassNotFoundException {
        final int packHeadLength = 2;
        // 把position指向0位置，把limit指向原来的position位置
        IoBuffer in = ctx.getBuffer();
        in.flip();
        IoBuffer resultBuffer;
        LoggerFactory.getLogger(getClass()).debug("收到数据：" + in.remaining());
        while (in.remaining() >= packHeadLength) {
            in.mark();
            // 先判断是否已经握手
            if (!session.containsAttribute(WebSocketUtils.SessionAttribute)) {
                // 第一次链接 读取头
                if (isServer) {// 服务器处理
                    if (tryWebSocketHandShake(session, in, out)) {
                        // WebSocket握手成功。
                        in.position(in.limit());
                        ((ActionDispatcher) session.getHandler()).handshakeSuccess(session);
                    } else {
                        in.reset();
                    }
                } else {
                    // 服务器返回的握手数据
                    String payLoadMsg = new String(in.array());
                    if (doHandShake(session, payLoadMsg)) {
                        // WebSocket握手成功。
                        in.position(in.limit());
                        ((ActionDispatcher) session.getHandler()).handshakeSuccess(session);
                    } else {
                        in.reset();
                    }
                }
                break;
            } else if (session.containsAttribute(WebSocketUtils.SessionAttribute)
                    && (Boolean) session.getAttribute(WebSocketUtils.SessionAttribute)) {
                // 有来自websocket的传入数据。解码并发送到处理程序或下一个筛选器。
//    			int startPos = in.position();
                resultBuffer = this.buildWSDataBuffer(in, session);
                LoggerFactory.getLogger(getClass()).debug("是否有可读数据：" + (resultBuffer != null));
                if (resultBuffer == null) {
                    // 缓冲区中没有足够的数据进行分析。重置缓冲区中的
                    // 定位并等待更多数据，然后重试。
                    in.reset();
                    break;
                }
//                System.out.println("remaining=" + resultBuffer.remaining());
                if (resultBuffer.remaining() == 0) {
                    break;
                }
                LoggerFactory.getLogger(getClass()).debug("未解压数据大小 bytes=" + resultBuffer.array().length);
                // 解压
                byte[] by = ZlibUtil.decompress(resultBuffer.array());
                LoggerFactory.getLogger(getClass()).debug("解密前数据大小 bytes=" + by.length);
                // 解密
                by = ctx.getAes().decrypt(by);
                Amf3Input input = Amf3Stream.newAmf3Input(by);
                ActionData<?> data = new ActionData<>(0);
                data.setVerify(input.readLong());
                data.setAction(input.readInt());
                if (session.containsAttribute(GameEnum.SERVLET_ID)) {
                    data.setSessionId(input.readLong());
                }
                int byteLen = input.readInt();// 获取长度
                // System.out.println(data+"len="+byteLen+", "+resultBuffer.array().length);
                byte[] bytes = new byte[byteLen];
                input.read(bytes, 0, byteLen);
                data.setBuf(bytes);
                out.write(data);
            } else {
                in.reset();
                break;
            }
        }
//        System.out.println("解析完成，是否还有未读的： " + in.hasRemaining());
        if (in.hasRemaining()) { // 如果还有剩余的
            // 将数据移到buffer的最前面
            IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true);
            temp.put(in);
            temp.flip();
            in.clear();
            in.put(temp);
        } else {// 如果数据已经处理完毕，进行清空
            in.clear();
        }
    }

    /**
     * 尝试将消息解析为websocket握手请求。如果是这样的请求，则发送相应的握手响应。
     */
    private boolean tryWebSocketHandShake(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {

        try {
            String payLoadMsg = new String(in.array());
            String socketKey = WebSocketUtils.getClientWSRequestKey(payLoadMsg);
            if (socketKey.length() <= 0) {
                LoggerFactory.getLogger(getClass()).debug("socketKey 长度不够 len=0");
                return false;
            }
            String challengeAccept = WebSocketUtils.getWebSocketKeyChallengeResponse(socketKey);
            WebSocketHandShakeResponse wsResponse = WebSocketUtils.buildWSHandshakeResponse(challengeAccept);
            session.write(wsResponse);
            session.setAttribute(WebSocketUtils.SessionAttribute, true);
            return true;
        } catch (Exception e) {
            LoggerFactory.getLogger(getClass()).debug("tryWebSocketHandShake", e);
            // input is not a websocket handshake request.
            // 握手失败
            ((ActionDispatcher) session.getHandler()).handshakeFail(session);
            return false;
        }
    }

    // Decode the in buffer according to the Section 5.2. RFC 6455
    // If there are multiple websocket dataframes in the buffer, this will parse
    // all and return one complete decoded buffer.
    private IoBuffer buildWSDataBuffer(IoBuffer in, IoSession session) {
        IoBuffer resultBuffer;
        byte frameInfo = in.get();
        byte opCode = (byte) (frameInfo & 0x0f);
        if (opCode == 8) {
            LoggerFactory.getLogger(getClass()).debug("opCode==8");
            // 操作码8表示关闭。见RFC 6455第5.2节
            // 返回迄今为止解析过的内容。
            session.closeNow();
            in.position(in.limit());
            return in;
        }
        int length = in.limit();
        if (length >= receiveMax) {
            LoggerFactory.getLogger(getClass()).debug("dealDecode解析长度len=" + length + ", 大于最大" + receiveMax + ", 该玩家被强制关闭");
            session.closeNow();
            in.position(in.limit());
            return in;
        }
        int frameLen = (in.get() & (byte) 0x7F); // 127
        if (frameLen == 126) {
            if (in.remaining() >= 6) {
                byte[] lenBytes = new byte[2];
                in.get(lenBytes);
                frameLen = byteArray2ToInt(lenBytes);
            } else {
                return null;
            }
        } else if (frameLen == 127) {
            if(in.remaining() >= 10){
                byte[] lenBytes = new byte[4];
                in.get(lenBytes);
                frameLen = byteArray8ToInt(lenBytes);
            }else{
                return null;
            }
        }
        LoggerFactory.getLogger(getClass()).debug("数据长度frameLen=" + frameLen);
        byte[] unMaskedPayLoad = new byte[frameLen];
        if (!isServer) { // 如果是客户端  不解析掩码
            if (frameLen != in.remaining()) {
                LoggerFactory.getLogger(getClass()).debug("数据长度不对，frameLen=" + frameLen + ", remaining=" + in.remaining());
                return null;
            }
            for (int i = 0; i < frameLen; i++) {
                unMaskedPayLoad[i] = in.get();
            }
        } else {
            //		System.out.println("opCode = "+opCode+" | frameLen = "+frameLen);
            // Validate if we have enough data in the buffer to completely
            // parse the WebSocket DataFrame. If not return null.
            if (frameLen + 4 > in.remaining()) {
//                System.out.println("frameLen=" + frameLen + ", remaining=" + in.remaining());
                return null;
            }
            // 掩码  服务器发送客户端没有掩码  客户端发送服务器有掩码
            byte mask[] = new byte[4];
            for (int i = 0; i < 4; i++) {
                mask[i] = in.get();
            }
            /*
             * now un-mask frameLen bytes as per Section 5.3 RFC 6455 Octet i of
             * the transformed data ("transformed-octet-i") is the XOR of octet
             * i of the original data ("original-octet-i") with octet at index i
             * modulo 4 of the masking key ("masking-key-octet-j"):
             *
             * j = i MOD 4 transformed-octet-i = original-octet-i XOR
             * masking-key-octet-j
             */
            for (int i = 0; i < frameLen; i++) {
                byte maskedByte = in.get();
                unMaskedPayLoad[i] = (byte) (maskedByte ^ mask[i % 4]);
            }
        }
        resultBuffer = IoBuffer.wrap(unMaskedPayLoad);
        resultBuffer.position(resultBuffer.limit());
        resultBuffer.setAutoExpand(true);
        resultBuffer.flip();
        return resultBuffer;
    }

    /**
     * 解析服务器发送的握手信息
     *
     * @param session
     * @param headerBuffer
     * @return
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    private boolean doHandShake(IoSession session, String headerBuffer)
            throws NoSuchAlgorithmException, UnsupportedEncodingException {
        Map<String, String> $header = parseHeader(headerBuffer);
        if (!$header.containsKey("Sec-WebSocket-Accept")) {
            session.closeNow();
            LoggerFactory.getLogger(getClass()).debug("收到的 Sec-WebSocket-Accept 不存在!");
            // 握手失败
            ((ActionDispatcher) session.getHandler()).handshakeFail(session);
            return false;
        }
        String sec = WebSocketUtils.getWebSocketKeyChallengeResponse(WebSocketUtils.key);
        if (!sec.equals($header.get("Sec-WebSocket-Accept"))) {
            session.closeNow();
            LoggerFactory.getLogger(getClass()).debug("服务器的 Sec-WebSocket-Accept 不存在!");
            // 握手失败
            ((ActionDispatcher) session.getHandler()).handshakeFail(session);
            return false;
        }
        session.setAttribute(WebSocketUtils.SessionAttribute, true);
        WebSocketUtils.header = $header;
        return true;
    }

    private Map<String, String> parseHeader(String data) {
        Map<String, String> $meta = new HashMap<>();
        String[] parts = data.split("\r\n\r\n", 2);
        // // parts[0] = HTTP头;
        // // parts[1] = HTTP主体，GET请求没有body
        String[] $headerLines = parts[0].split("\r\n");
        // // HTTP协议头,方法，路径，协议[RFC-2616 5.1]
        String[] $data = $headerLines[0].split(" ", 3);
        $meta.put("method", $data[0]);
        $meta.put("uri", $data[1]);
        $meta.put("protocol", $data[2]);
        // 错误的HTTP请求
        if ($data[0] == null || $data[1] == null || $data[2] == null) {
            LoggerFactory.getLogger(getClass()).debug("parseHeader 解析传送数据错误!");
            return null;
        }
        // 解析Header
        $meta.putAll(parseHeaderLine($headerLines));
        return $meta;
    }

    private Map<String, String> parseHeaderLine(Object $headerLines) {
        if ($headerLines instanceof String) {
            $headerLines = ((String) $headerLines).split("\r\n");
        }
        Map<String, String> $header = new HashMap<>();
        String[] $_r, $keys;
        String $key, $value;
        for (String $_h : (String[]) $headerLines) {
            $_h = $_h.trim();
            if ($_h == null)
                continue;
            $_r = $_h.split(":", 2);
            // 头字段名称首字母大写
            $keys = $_r[0].split("-");
            // $keys = ucfirst($keys);
            $key = StringUtils.join($keys, "-");
            $value = $_r.length > 1 ? $_r[1] : "";
            $header.put($key.trim(), $value.trim());
        }
        return $header;
    }

    // private String[] ucfirst(String[] $keys) {
    // for (int i = 0; i < $keys.length; i++) {
    // $keys[i] = $keys[i].toLowerCase();
    // }
    // return $keys;
    // }

    @Override
    public void dispose(IoSession session) throws Exception {
        Context ctx = (Context) session.getAttribute(CONTEXT);
        if (ctx != null) {
            session.removeAttribute(CONTEXT);
        }
    }

    @Override
    public void finishDecode(IoSession arg0, ProtocolDecoderOutput arg1) throws Exception {

    }

    public static int byteArray2ToInt(byte[] b) {
        return b[1] & 0xFF | (b[0] & 0xFF) << 8;
    }

    public static int byteArray2ToShort(byte[] b) {
        return b[1] & 0xFF | (b[0] & 0xFF) << 8;
    }

    public static int byteArray8ToInt(byte[] b) {
        return b[7] & 0xFF | (b[6] & 0xFF) << 8 | (b[5] & 0xFF) << 16 | (b[4] & 0xFF) << 24;
    }

}
